Data import and Data cleaning
This section includes the code for downloading the data & various data cleansing steps:
- Performed data de-duplication as there were many duplicate records
- Filter out all houses with price less than € 7.000 as these are probably typing mistakes (based on various real estate site)
The original dataset has 15447 records and the cleaned dataset 15353 records.
library(tidyverse)
library(DataExplorer)
library(DT)
# Read dataset
original_span <- read_csv("https://sg-exercise.s3-eu-west-1.amazonaws.com/assignment_rev2.csv")
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
.default = col_logical(),
id = col_double(),
ranking_score = col_double(),
agent_id = col_double(),
geography_name = col_character(),
sq_meters = col_double(),
price = col_double(),
year_of_construction = col_double(),
floor = col_character(),
subtype = col_character(),
rooms = col_double(),
no_of_bathrooms = col_double(),
energy_class = col_character(),
renovation_year = col_double(),
no_of_wc = col_double(),
ad_type = col_character(),
living_rooms = col_double(),
kitchens = col_double(),
balcony_area = col_double()
)
ℹ Use `spec()` for the full column specifications.
# Data cleaning
span <-
original_span %>%
distinct(across(c(-id, -agent_id, -ranking_score)), .keep_all = TRUE) %>% # Select unique records
mutate(year_of_construction = na_if(year_of_construction, 2155)) %>% # Convert 2155 to missing value
filter(price >= 7000) # Filter price based on the minimum house price... 8000 on various Greek websites
nrow(original_span)
[1] 15447
nrow(span)
[1] 15353
Assignment Part 1
It includes the r code and the table of summarized results.
span %>%
group_by(subtype, geography_name) %>%
summarise(N = n(), mean_price = round(mean(price), 0), median_price = round(median(price), 0), sd_price = round(sd(price), 0)) %>%
datatable(filter = 'top') %>%
formatCurrency(c('mean_price', 'median_price', 'sd_price'), currency = "€", digits = 0)
`summarise()` regrouping output by 'subtype' (override with `.groups` argument)
Assignment Part 3
Since the target is to develop a machine learning model we have to take a good look on which features we should use. The following plot shows the proportion of missing values per variable

We should definitely exclude equipped variable since all values are missing. Also, the id & agent_id were excluded beacause these are not useful in a model. Furthermore since there are a lot of variables with missing values, i decided to exclude all these variables. In a next step (with more available time) these variables could be included with the appropriate treatment e.g. imputation, encoding.
I also deleted all records that the construction year is missing. It is vital in order to include this variable in the model. Also deleted all records that construction year is before 1900 (typing errors).
The dataset used for the modeling has 14445 records.
nrow(span_model)
[1] 14445
_Develop a GBM model with grid search
I used the gbm package to develop a gradient boosting machine model. But instead of manually tweaking hyperparameters one at a time, i created a grid search which iterates over every combination of hyperparameter values and develops multiple models (81 in our case) so i can choose the best one.
So after selecting the best model (lowest RMSE - € 427,434) i developed the final model using the specified hyperparameters & using 10-fold cross validation. Then we print a feature importance plot. The most important feature seems to be the size of the property (square meters). The construction year & number of rooms are very important. Then geography_name, ranking_score, subtype & number of bathrooms are somewhat imortant but the rest seems to be unimportant.
set.seed(123)
# train GBM model
gbm.final <- gbm(
formula = price ~ .,
distribution = "gaussian",
data = random_span_model,
n.trees = 5732,
interaction.depth = 3,
shrinkage = 0.1,
n.minobsinnode = 7,
bag.fraction = .8,
train.fraction = 1,
cv.folds = 10,
n.cores = NULL,
verbose = FALSE
)
vip::vip(gbm.final) +
labs(title = "Feature importance plot",
subtitle = "Higher importance means more important in prediction model")

NA
NA
Now let’s say we want to predict the value of a new property. After creating the dataset by inserting the variables of the property, we run the prediction and it returns a predicted value of € 223,073.9
new_data <-
structure(list(
ranking_score = 122,
geography_name = structure(3L, .Label = c("beesy neighborhood", "gentrification area", "northern sub", "south beach"), class = "factor"),
sq_meters = 95,
price = 230000,
year_of_construction = 2007,
subtype = structure(1L, .Label = c("apartment", "apartment complex", "building", "bungalow", "detached", "houseboat", "loft", "maisonette", "other residential", "studio", "villa"), class = "factor"),
rooms = 3,
no_of_bathrooms = 1,
no_of_wc = 1,
ad_type = structure(2L, .Label = c("premium", "simple", "star", "up"), class = "factor"),
living_rooms = 0,
kitchens = 1,
balcony_area = 0),
class = "data.frame", row.names = c(NA, -1L))
# predict values for test data
pred_new <- predict(gbm.final, n.trees = gbm.final$n.trees, new_data)
pred_new
[1] 223073.9
In this case i used the same model for prediction & inference. Ιf there is more time, different algorithms could be applied and develop different models e.g Linear models for inference.
Of course we need further work to develop a reliable model that predicts the house price with better accuracy. I would take the following actions to improve the model:
- Prepare a better strategy for feature selection. I would try various methods from intrinsic(e.g. more models), filter(e.g. check statistical significance) & wrapper classes (e.g. backwords/forward elimination) of feature selection.
- Work a lot in feature engineering. E.g summarize categories, encode to nominal in categorical variables. Also try transformations (scaling, smoothing, binning etc.) in numerical predictors & try some 2-way or even 3-way interactions.
- Try more models with further hyperparameters tuning. e.g. Random Forest, Deep neural networks
- Create a shiny application (interactive web dashboards) in order to present the results to the stakeholders. This is a sample of a forecasting application and a clustering application i have created in the past.
Assignment Part 2
A very important metric to show the competitiveness is the price. The following table lists some basic price measures per area
span %>%
group_by(geography_name) %>%
summarise(N = n(),
mean_price = round(mean(price), 0),
sd_price = round(sd(price), 0),
min_price = min(price),
quantile_25 = quantile(price, probs = .25),
median = median(price),
quantile_75 = quantile(price, probs = .75),
max_price = max(price)
) %>%
datatable()
`summarise()` ungrouping output (override with `.groups` argument)
library(plotly)
library(ggthemes)
library(scales)
ggplotly(
ggplot(span, aes(price, fill = geography_name)) +
geom_density(alpha = 0.5) +
scale_x_log10(labels = dollar_format(suffix = "€", prefix = "")) +
theme_fivethirtyeight() +
scale_fill_tableau() +
theme(legend.title = element_blank()) +
labs(title = "Property prices distribution per area")
)
plotly.js does not (yet) support horizontal legend items
You can track progress here:
https://github.com/plotly/plotly.js/issues/53
Some next steps would be to discover other measures and check how different these are per area. At the end it will be a good idea to develop a single KPI to measure the competitiveness of each area. This KPI would combine all significant measures we have discovered. This would definitely need close cooperation with business stakeholders and house market experts.
LS0tCnRpdGxlOiAiU3BhTiByZWFsIGVzdGF0ZSBhbmFseXNpcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgRGF0YSBpbXBvcnQgYW5kIERhdGEgY2xlYW5pbmcKClRoaXMgc2VjdGlvbiBpbmNsdWRlcyB0aGUgY29kZSBmb3IgZG93bmxvYWRpbmcgdGhlIGRhdGEgJiB2YXJpb3VzIGRhdGEgCmNsZWFuc2luZyBzdGVwczogCgotIFBlcmZvcm1lZCBkYXRhIGRlLWR1cGxpY2F0aW9uIGFzIHRoZXJlIHdlcmUgbWFueSBkdXBsaWNhdGUgcmVjb3JkcyAKLSBGaWx0ZXIgb3V0IGFsbCBob3VzZXMgd2l0aCBwcmljZSBsZXNzIHRoYW4g4oKsIDcuMDAwIGFzIHRoZXNlIGFyZSBwcm9iYWJseSB0eXBpbmcgCm1pc3Rha2VzIChiYXNlZCBvbiB2YXJpb3VzIHJlYWwgZXN0YXRlIHNpdGUpCgpUaGUgb3JpZ2luYWwgZGF0YXNldCBoYXMgMTU0NDcgcmVjb3JkcyBhbmQgdGhlIGNsZWFuZWQgZGF0YXNldCAxNTM1MyByZWNvcmRzLgoKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEYXRhRXhwbG9yZXIpCmxpYnJhcnkoRFQpCgojIFJlYWQgZGF0YXNldApvcmlnaW5hbF9zcGFuIDwtIHJlYWRfY3N2KCJodHRwczovL3NnLWV4ZXJjaXNlLnMzLWV1LXdlc3QtMS5hbWF6b25hd3MuY29tL2Fzc2lnbm1lbnRfcmV2Mi5jc3YiKQoKIyBEYXRhIGNsZWFuaW5nCnNwYW4gPC0KICBvcmlnaW5hbF9zcGFuICU+JQogIGRpc3RpbmN0KGFjcm9zcyhjKC1pZCwgLWFnZW50X2lkLCAtcmFua2luZ19zY29yZSkpLCAua2VlcF9hbGwgPSBUUlVFKSAlPiUgIyBTZWxlY3QgdW5pcXVlIHJlY29yZHMKICBtdXRhdGUoeWVhcl9vZl9jb25zdHJ1Y3Rpb24gPSBuYV9pZih5ZWFyX29mX2NvbnN0cnVjdGlvbiwgMjE1NSkpICU+JSAjIENvbnZlcnQgMjE1NSB0byBtaXNzaW5nIHZhbHVlCiAgZmlsdGVyKHByaWNlID49IDcwMDApICMgRmlsdGVyIHByaWNlIGJhc2VkIG9uIHRoZSBtaW5pbXVtIGhvdXNlIHByaWNlCgpucm93KG9yaWdpbmFsX3NwYW4pCm5yb3coc3BhbikKCmBgYAoKCgojIEFzc2lnbm1lbnQgUGFydCAxCgpJdCBpbmNsdWRlcyB0aGUgciBjb2RlIGFuZCB0aGUgdGFibGUgb2Ygc3VtbWFyaXplZCByZXN1bHRzLiAKCmBgYHtyfQoKc3BhbiAlPiUKICBncm91cF9ieShzdWJ0eXBlLCBnZW9ncmFwaHlfbmFtZSkgJT4lCiAgc3VtbWFyaXNlKE4gPSBuKCksIG1lYW5fcHJpY2UgPSByb3VuZChtZWFuKHByaWNlKSwgMCksIG1lZGlhbl9wcmljZSA9IHJvdW5kKG1lZGlhbihwcmljZSksIDApLCBzZF9wcmljZSA9IHJvdW5kKHNkKHByaWNlKSwgMCkpICU+JSAKICBkYXRhdGFibGUoZmlsdGVyID0gJ3RvcCcpICU+JSAKICBmb3JtYXRDdXJyZW5jeShjKCdtZWFuX3ByaWNlJywgJ21lZGlhbl9wcmljZScsICdzZF9wcmljZScpLCBjdXJyZW5jeSA9ICLigqwiLCBkaWdpdHMgPSAwKQoKYGBgCgojIEFzc2lnbm1lbnQgUGFydCAzIAoKU2luY2UgdGhlIHRhcmdldCBpcyB0byBkZXZlbG9wIGEgbWFjaGluZSBsZWFybmluZyBtb2RlbCB3ZSBoYXZlIHRvIHRha2UgYSBnb29kIGxvb2sgCm9uIHdoaWNoIGZlYXR1cmVzIHdlIHNob3VsZCB1c2UuIFRoZSBmb2xsb3dpbmcgcGxvdCBzaG93cyB0aGUgcHJvcG9ydGlvbiBvZiBtaXNzaW5nCnZhbHVlcyBwZXIgdmFyaWFibGUKCgpgYGB7ciBmaWcud2lkdGg9MTB9CgpwbG90X21pc3Npbmcoc3BhbiwgbWlzc2luZ19vbmx5ID0gVFJVRSwgdGl0bGUgPSAiTWlzc2luZyB2YWx1ZXMgJSBwZXIgdmFyaWFibGUiKQoKYGBgCgoKV2Ugc2hvdWxkIGRlZmluaXRlbHkgZXhjbHVkZSAqKmVxdWlwcGVkKiogdmFyaWFibGUgc2luY2UgYWxsIHZhbHVlcyBhcmUgbWlzc2luZy4gQWxzbywKdGhlICoqaWQqKiAmICoqYWdlbnRfaWQqKiB3ZXJlIGV4Y2x1ZGVkIGJlYWNhdXNlIHRoZXNlIGFyZSBub3QgdXNlZnVsIGluIGEgbW9kZWwuIApGdXJ0aGVybW9yZSBzaW5jZSB0aGVyZSBhcmUgYSBsb3Qgb2YgdmFyaWFibGVzIHdpdGggbWlzc2luZyB2YWx1ZXMsIGkgZGVjaWRlZCB0bwoqKmV4Y2x1ZGUgYWxsIHRoZXNlIHZhcmlhYmxlcyoqLiBJbiBhIG5leHQgc3RlcCAod2l0aCBtb3JlIGF2YWlsYWJsZSB0aW1lKSB0aGVzZSAKdmFyaWFibGVzIGNvdWxkIGJlIGluY2x1ZGVkIHdpdGggdGhlIGFwcHJvcHJpYXRlIHRyZWF0bWVudCBlLmcuIGltcHV0YXRpb24sIGVuY29kaW5nLiAgCkkgYWxzbyBkZWxldGVkIGFsbCByZWNvcmRzIHRoYXQgdGhlIGNvbnN0cnVjdGlvbiB5ZWFyIGlzIG1pc3NpbmcuIEl0IGlzIHZpdGFsIGluIApvcmRlciB0byBpbmNsdWRlIHRoaXMgdmFyaWFibGUgaW4gdGhlIG1vZGVsLiBBbHNvIGRlbGV0ZWQgYWxsIHJlY29yZHMgdGhhdCBjb25zdHJ1Y3Rpb24gCnllYXIgaXMgYmVmb3JlIDE5MDAgKHR5cGluZyBlcnJvcnMpLiAKClRoZSBkYXRhc2V0IHVzZWQgZm9yIHRoZSBtb2RlbGluZyBoYXMgMTQ0NDUgcmVjb3Jkcy4KCmBgYHtyfQojIENyZWF0ZSB0aGUgZGF0YXNldCBmb3IgdGhlIG1vZGVsaW5nIHBoYXNlCnNwYW5fbW9kZWwgPC0gCiAgc3BhbiAlPiUgCiAgc2VsZWN0KC1lcXVpcHBlZCwgLWlkLCAtYWdlbnRfaWQpICU+JSAKICBmaWx0ZXIoaXMubmEoeWVhcl9vZl9jb25zdHJ1Y3Rpb24pID09IEZBTFNFICYgeWVhcl9vZl9jb25zdHJ1Y3Rpb24gPiAxOTAwKSAlPiUgCiAgc2VsZWN0KHJhbmtpbmdfc2NvcmUsIGdlb2dyYXBoeV9uYW1lLCBzcV9tZXRlcnMsIHByaWNlLCB5ZWFyX29mX2NvbnN0cnVjdGlvbiwKICAgICAgIHN1YnR5cGUsIHJvb21zLCBub19vZl9iYXRocm9vbXMsIG5vX29mX3djLCBhZF90eXBlLCBsaXZpbmdfcm9vbXMsIAogICAgICAga2l0Y2hlbnMsIGJhbGNvbnlfYXJlYSkgCiAgCm5yb3coc3Bhbl9tb2RlbCkKYGBgCgoKIyBfRGV2ZWxvcCBhIEdCTSBtb2RlbCB3aXRoIGdyaWQgc2VhcmNoCgpJIHVzZWQgdGhlIGdibSBwYWNrYWdlIHRvIGRldmVsb3AgYSBncmFkaWVudCBib29zdGluZyBtYWNoaW5lIG1vZGVsLiBCdXQgaW5zdGVhZCBvZiAKbWFudWFsbHkgdHdlYWtpbmcgaHlwZXJwYXJhbWV0ZXJzIG9uZSBhdCBhIHRpbWUsIGkgY3JlYXRlZCBhIGdyaWQgc2VhcmNoIHdoaWNoIAppdGVyYXRlcyBvdmVyIGV2ZXJ5IGNvbWJpbmF0aW9uIG9mIGh5cGVycGFyYW1ldGVyIHZhbHVlcyBhbmQgZGV2ZWxvcHMgbXVsdGlwbGUgCm1vZGVscyAoODEgaW4gb3VyIGNhc2UpIHNvIGkgY2FuIGNob29zZSB0aGUgYmVzdCBvbmUuCgoKYGBge3J9CgpsaWJyYXJ5KGdibSkKCnNwYW5fbW9kZWwgPC0gCiAgYXMuZGF0YS5mcmFtZSh1bmNsYXNzKHNwYW5fbW9kZWwpLCBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkKCgojIGNyZWF0ZSBoeXBlcnBhcmFtZXRlciBncmlkCmh5cGVyX2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgc2hyaW5rYWdlID0gYyguMDEsIC4wNSwgLjEpLAogIGludGVyYWN0aW9uLmRlcHRoID0gYygzLCA1LCA3KSwKICBuLm1pbm9ic2lubm9kZSA9IGMoNSwgNywgMTApLAogIGJhZy5mcmFjdGlvbiA9IGMoLjY1LCAuOCwgMSksIAogIG9wdGltYWxfdHJlZXMgPSAwLCAgICAgICAgICAgICAgICMgYSBwbGFjZSB0byBkdW1wIHJlc3VsdHMKICBtaW5fUk1TRSA9IDAgICAgICAgICAgICAgICAgICAgICAjIGEgcGxhY2UgdG8gZHVtcCByZXN1bHRzCikKCgojIHJhbmRvbWl6ZSBkYXRhCnJhbmRvbV9pbmRleCA8LSBzYW1wbGUoMTpucm93KHNwYW5fbW9kZWwpLCBucm93KHNwYW5fbW9kZWwpKQpyYW5kb21fc3Bhbl9tb2RlbCA8LSBzcGFuX21vZGVsW3JhbmRvbV9pbmRleCwgXQoKCiMgZ3JpZCBzZWFyY2ggCmZvcihpIGluIDE6bnJvdyhoeXBlcl9ncmlkKSkgewogIAogICMgcmVwcm9kdWNpYmlsaXR5CiAgc2V0LnNlZWQoMTIzKQogIAogICMgdHJhaW4gbW9kZWwKICBnYm0udHVuZSA8LSBnYm0oCiAgICBmb3JtdWxhID0gcHJpY2UgfiAuLAogICAgZGlzdHJpYnV0aW9uID0gImdhdXNzaWFuIiwKICAgIGRhdGEgPSByYW5kb21fc3Bhbl9tb2RlbCwKICAgIG4udHJlZXMgPSA2MDAwLAogICAgaW50ZXJhY3Rpb24uZGVwdGggPSBoeXBlcl9ncmlkJGludGVyYWN0aW9uLmRlcHRoW2ldLAogICAgc2hyaW5rYWdlID0gaHlwZXJfZ3JpZCRzaHJpbmthZ2VbaV0sCiAgICBuLm1pbm9ic2lubm9kZSA9IGh5cGVyX2dyaWQkbi5taW5vYnNpbm5vZGVbaV0sCiAgICBiYWcuZnJhY3Rpb24gPSBoeXBlcl9ncmlkJGJhZy5mcmFjdGlvbltpXSwKICAgIHRyYWluLmZyYWN0aW9uID0gLjc1LAogICAgbi5jb3JlcyA9IE5VTEwsICMgd2lsbCB1c2UgYWxsIGNvcmVzIGJ5IGRlZmF1bHQKICAgIHZlcmJvc2UgPSBGQUxTRQogICkKICAKICAjIGFkZCBtaW4gdHJhaW5pbmcgZXJyb3IgYW5kIHRyZWVzIHRvIGdyaWQKICBoeXBlcl9ncmlkJG9wdGltYWxfdHJlZXNbaV0gPC0gd2hpY2gubWluKGdibS50dW5lJHZhbGlkLmVycm9yKQogIGh5cGVyX2dyaWQkbWluX1JNU0VbaV0gPC0gc3FydChtaW4oZ2JtLnR1bmUkdmFsaWQuZXJyb3IpKQp9CgoKIyBzYXZlKGh5cGVyX2dyaWQsIGZpbGUgPSAiLi9kYXRhL2h5cGVyX2dyaWQuUmRhIikKCmh5cGVyX2dyaWQgJT4lIAogIGFycmFuZ2UobWluX1JNU0UpICU+JSAKICBkYXRhdGFibGUoKQoKCmBgYAoKU28gYWZ0ZXIgc2VsZWN0aW5nIHRoZSBiZXN0IG1vZGVsIChsb3dlc3QgUk1TRSAtIOKCrCA0MjcsNDM0KSBpIGRldmVsb3BlZCB0aGUgZmluYWwgbW9kZWwgCnVzaW5nIHRoZSBzcGVjaWZpZWQgaHlwZXJwYXJhbWV0ZXJzICYgdXNpbmcgMTAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uLiAKVGhlbiB3ZSBwcmludCBhIGZlYXR1cmUgaW1wb3J0YW5jZSBwbG90LiBUaGUgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZSBzZWVtcyB0byBiZSAKdGhlICoqc2l6ZSBvZiB0aGUgcHJvcGVydHkqKiAoc3F1YXJlIG1ldGVycykuIFRoZSAqKmNvbnN0cnVjdGlvbiB5ZWFyKiogCiYgKipudW1iZXIgb2Ygcm9vbXMqKiBhcmUgdmVyeSBpbXBvcnRhbnQuIFRoZW4gKipnZW9ncmFwaHlfbmFtZSoqLCAqKnJhbmtpbmdfc2NvcmUqKiwKKipzdWJ0eXBlKiogJiAqKm51bWJlciBvZiBiYXRocm9vbXMqKiBhcmUgc29tZXdoYXQgaW1vcnRhbnQgYnV0IHRoZSByZXN0IHNlZW1zIHRvIApiZSB1bmltcG9ydGFudC4KICAKCmBgYHtyfQoKc2V0LnNlZWQoMTIzKQoKIyB0cmFpbiBHQk0gbW9kZWwKZ2JtLmZpbmFsIDwtIGdibSgKICBmb3JtdWxhID0gcHJpY2UgfiAuLAogIGRpc3RyaWJ1dGlvbiA9ICJnYXVzc2lhbiIsCiAgZGF0YSA9IHJhbmRvbV9zcGFuX21vZGVsLAogIG4udHJlZXMgPSA1NzMyLAogIGludGVyYWN0aW9uLmRlcHRoID0gMywKICBzaHJpbmthZ2UgPSAwLjEsCiAgbi5taW5vYnNpbm5vZGUgPSA3LAogIGJhZy5mcmFjdGlvbiA9IC44LCAKICB0cmFpbi5mcmFjdGlvbiA9IDEsCiAgY3YuZm9sZHMgPSAxMCwKICBuLmNvcmVzID0gTlVMTCwgCiAgdmVyYm9zZSA9IEZBTFNFCiAgKSAgCgoKdmlwOjp2aXAoZ2JtLmZpbmFsKSArCiAgbGFicyh0aXRsZSA9ICJGZWF0dXJlIGltcG9ydGFuY2UgcGxvdCIsCiAgICAgICBzdWJ0aXRsZSA9ICJIaWdoZXIgaW1wb3J0YW5jZSBtZWFucyBtb3JlIGltcG9ydGFudCBpbiBwcmVkaWN0aW9uIG1vZGVsIikKCgpgYGAKCk5vdyBsZXQncyBzYXkgd2Ugd2FudCB0byBwcmVkaWN0IHRoZSB2YWx1ZSBvZiBhIG5ldyBwcm9wZXJ0eS4gQWZ0ZXIgY3JlYXRpbmcgdGhlIGRhdGFzZXQKYnkgaW5zZXJ0aW5nIHRoZSB2YXJpYWJsZXMgb2YgdGhlIHByb3BlcnR5LCB3ZSBydW4gdGhlIHByZWRpY3Rpb24gYW5kIGl0IHJldHVybnMgCmEgcHJlZGljdGVkIHZhbHVlIG9mIOKCrCAyMjMsMDczLjkgCgoKYGBge3J9CgoKbmV3X2RhdGEgPC0gCnN0cnVjdHVyZShsaXN0KAogICAgcmFua2luZ19zY29yZSA9IDEyMiwgCiAgICBnZW9ncmFwaHlfbmFtZSA9IHN0cnVjdHVyZSgzTCwgLkxhYmVsID0gYygiYmVlc3kgbmVpZ2hib3Job29kIiwgImdlbnRyaWZpY2F0aW9uIGFyZWEiLCAibm9ydGhlcm4gc3ViIiwgInNvdXRoIGJlYWNoIiksIGNsYXNzID0gImZhY3RvciIpLCAKICAgIHNxX21ldGVycyA9IDk1LCAKICAgIHByaWNlID0gMjMwMDAwLCAKICAgIHllYXJfb2ZfY29uc3RydWN0aW9uID0gMjAwNywgCiAgICBzdWJ0eXBlID0gc3RydWN0dXJlKDFMLCAuTGFiZWwgPSBjKCJhcGFydG1lbnQiLCAiYXBhcnRtZW50IGNvbXBsZXgiLCAiYnVpbGRpbmciLCAiYnVuZ2Fsb3ciLCAiZGV0YWNoZWQiLCAiaG91c2Vib2F0IiwgImxvZnQiLCAibWFpc29uZXR0ZSIsICJvdGhlciByZXNpZGVudGlhbCIsICJzdHVkaW8iLCAidmlsbGEiKSwgY2xhc3MgPSAiZmFjdG9yIiksIAogICAgcm9vbXMgPSAzLCAKICAgIG5vX29mX2JhdGhyb29tcyA9IDEsIAogICAgbm9fb2Zfd2MgPSAxLCAKICAgIGFkX3R5cGUgPSBzdHJ1Y3R1cmUoMkwsIC5MYWJlbCA9IGMoInByZW1pdW0iLCAic2ltcGxlIiwgInN0YXIiLCAidXAiKSwgY2xhc3MgPSAiZmFjdG9yIiksIAogICAgbGl2aW5nX3Jvb21zID0gMCwgCiAgICBraXRjaGVucyA9IDEsIAogICAgYmFsY29ueV9hcmVhID0gMCksIAogICAgY2xhc3MgPSAiZGF0YS5mcmFtZSIsIHJvdy5uYW1lcyA9IGMoTkEsIC0xTCkpCgoKCiMgcHJlZGljdCB2YWx1ZXMgZm9yIHRlc3QgZGF0YQpwcmVkX25ldyA8LSBwcmVkaWN0KGdibS5maW5hbCwgbi50cmVlcyA9IGdibS5maW5hbCRuLnRyZWVzLCBuZXdfZGF0YSkKCnByZWRfbmV3CgpgYGAKCgpJbiB0aGlzIGNhc2UgaSB1c2VkIHRoZSBzYW1lIG1vZGVsIGZvciBwcmVkaWN0aW9uICYgaW5mZXJlbmNlLiDOmWYgdGhlcmUgaXMgbW9yZQp0aW1lLCBkaWZmZXJlbnQgYWxnb3JpdGhtcyBjb3VsZCBiZSBhcHBsaWVkIGFuZCBkZXZlbG9wIGRpZmZlcmVudCBtb2RlbHMgZS5nIExpbmVhcgptb2RlbHMgZm9yIGluZmVyZW5jZS4KCk9mIGNvdXJzZSB3ZSBuZWVkIGZ1cnRoZXIgd29yayB0byBkZXZlbG9wIGEgcmVsaWFibGUgbW9kZWwgdGhhdCBwcmVkaWN0cwp0aGUgaG91c2UgcHJpY2Ugd2l0aCBiZXR0ZXIgYWNjdXJhY3kuIEkgd291bGQgdGFrZSB0aGUgZm9sbG93aW5nIGFjdGlvbnMgdG8gCmltcHJvdmUgdGhlIG1vZGVsOiAgCgotIFByZXBhcmUgYSBiZXR0ZXIgc3RyYXRlZ3kgZm9yICoqZmVhdHVyZSBzZWxlY3Rpb24qKi4gSSB3b3VsZCB0cnkgdmFyaW91cyBtZXRob2RzCmZyb20gaW50cmluc2ljKGUuZy4gbW9yZSBtb2RlbHMpLCBmaWx0ZXIoZS5nLiBjaGVjayBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2UpICYgCndyYXBwZXIgY2xhc3NlcyAoZS5nLiBiYWNrd29yZHMvZm9yd2FyZCBlbGltaW5hdGlvbikgb2YgZmVhdHVyZSBzZWxlY3Rpb24uCi0gV29yayBhIGxvdCBpbiAqKmZlYXR1cmUgZW5naW5lZXJpbmcqKi4gRS5nIHN1bW1hcml6ZSBjYXRlZ29yaWVzLCBlbmNvZGUgdG8gbm9taW5hbCAKaW4gY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBBbHNvIHRyeSB0cmFuc2Zvcm1hdGlvbnMgKHNjYWxpbmcsIHNtb290aGluZywgYmlubmluZyBldGMuKSAKaW4gbnVtZXJpY2FsIHByZWRpY3RvcnMgJiB0cnkgc29tZSAyLXdheSBvciBldmVuIDMtd2F5IGludGVyYWN0aW9ucy4gIAotIFRyeSAqKm1vcmUgbW9kZWxzKiogd2l0aCBmdXJ0aGVyIGh5cGVycGFyYW1ldGVycyB0dW5pbmcuIGUuZy4gUmFuZG9tIEZvcmVzdCwgRGVlcCAKbmV1cmFsIG5ldHdvcmtzIAotIENyZWF0ZSBhIHNoaW55IGFwcGxpY2F0aW9uIChpbnRlcmFjdGl2ZSB3ZWIgZGFzaGJvYXJkcykgaW4gb3JkZXIgdG8gcHJlc2VudCB0aGUgcmVzdWx0cyB0byB0aGUgc3Rha2Vob2xkZXJzLiAKVGhpcyBpcyBhIHNhbXBsZSBvZiBhIFtmb3JlY2FzdGluZyBhcHBsaWNhdGlvbl0oaHR0cHM6Ly9tYW50b25pb3Uuc2hpbnlhcHBzLmlvL0ZvcmVjYXN0aW5nLykgYW5kIGEgW2NsdXN0ZXJpbmcgYXBwbGljYXRpb25dKGh0dHBzOi8vbWFudG9uaW91LnNoaW55YXBwcy5pby9DbHVzdGVyaW5nLykgaSBoYXZlIGNyZWF0ZWQgaW4gdGhlIHBhc3QuCgoKCiMgQXNzaWdubWVudCBQYXJ0IDIKCkEgdmVyeSBpbXBvcnRhbnQgbWV0cmljIHRvIHNob3cgdGhlIGNvbXBldGl0aXZlbmVzcyBpcyB0aGUgcHJpY2UuIFRoZSBmb2xsb3dpbmcKdGFibGUgbGlzdHMgc29tZSBiYXNpYyBwcmljZSBtZWFzdXJlcyBwZXIgYXJlYQoKYGBge3J9CgpzcGFuICU+JSAKICBncm91cF9ieShnZW9ncmFwaHlfbmFtZSkgJT4lIAogIHN1bW1hcmlzZShOID0gbigpLAogICAgICAgICAgICBtZWFuX3ByaWNlID0gcm91bmQobWVhbihwcmljZSksIDApLCAKICAgICAgICAgICAgc2RfcHJpY2UgPSByb3VuZChzZChwcmljZSksIDApLAogICAgICAgICAgICBtaW5fcHJpY2UgPSBtaW4ocHJpY2UpLAogICAgICAgICAgICBxdWFudGlsZV8yNSA9IHF1YW50aWxlKHByaWNlLCBwcm9icyA9IC4yNSksCiAgICAgICAgICAgIG1lZGlhbiA9IG1lZGlhbihwcmljZSksCiAgICAgICAgICAgIHF1YW50aWxlXzc1ID0gcXVhbnRpbGUocHJpY2UsIHByb2JzID0gLjc1KSwKICAgICAgICAgICAgbWF4X3ByaWNlID0gbWF4KHByaWNlKQogICAgICAgICAgICApICU+JQogIGRhdGF0YWJsZSgpCgoKCgpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD0xMH0KbGlicmFyeShwbG90bHkpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkoc2NhbGVzKQoKCmdncGxvdGx5KAogICAgICAgICBnZ3Bsb3Qoc3BhbiwgYWVzKHByaWNlLCBmaWxsID0gZ2VvZ3JhcGh5X25hbWUpKSArCiAgICAgICAgIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKwogICAgICAgICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IGRvbGxhcl9mb3JtYXQoc3VmZml4ID0gIuKCrCIsIHByZWZpeCA9ICIiKSkgKwogICAgICAgICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKSArCiAgICAgICAgIHNjYWxlX2ZpbGxfdGFibGVhdSgpICsKICAgICAgICAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgICAgICAgIGxhYnModGl0bGUgPSAiUHJvcGVydHkgcHJpY2VzIGRpc3RyaWJ1dGlvbiBwZXIgYXJlYSIpCiAgICAgICAgICApCgpgYGAKClNvbWUgbmV4dCBzdGVwcyB3b3VsZCBiZSB0byBkaXNjb3ZlciBvdGhlciBtZWFzdXJlcyBhbmQgY2hlY2sgaG93IGRpZmZlcmVudCB0aGVzZQphcmUgcGVyIGFyZWEuIEF0IHRoZSBlbmQgaXQgd2lsbCBiZSBhIGdvb2QgaWRlYSB0byBkZXZlbG9wIGEgc2luZ2xlIEtQSSB0byBtZWFzdXJlIHRoZQpjb21wZXRpdGl2ZW5lc3Mgb2YgZWFjaCBhcmVhLiBUaGlzIEtQSSB3b3VsZCBjb21iaW5lIGFsbCBzaWduaWZpY2FudCBtZWFzdXJlcyAKd2UgaGF2ZSBkaXNjb3ZlcmVkLiAKVGhpcyB3b3VsZCBkZWZpbml0ZWx5IG5lZWQgY2xvc2UgY29vcGVyYXRpb24gd2l0aCBidXNpbmVzcyBzdGFrZWhvbGRlcnMgYW5kCmhvdXNlIG1hcmtldCBleHBlcnRzLgoK